# -*- coding: utf-8 -*-

"""
Moduł odpowiadający za kreację piłeczki przemieszczającej się w ograniczonym 
obszarze z odpowiednią prędkością i w odpowiednim kierunku.

B{Moduł zawiera:}
    1. Klasa Ball
        - __init__
        - getPosition
        - setPosition
        - setPositionX
        - setPositionY
        - setVelocity
        - getVelocity
        - getRadius
        - getMass
        - collidesWith
        - collidesWithWall
        - draw
"""

from __future__ import unicode_literals
import wx
from math import sqrt
from copy import deepcopy

from Vectors import Velocity
from Point import Point



class Ball(object):
    """
    Klasa reprezentująca obiekt piłeczki poruszającej się po ekranie z
    odpowiednią prędkością i w odpowiednim kierunku.
    """
    
    # TODO: losowe kolory piłeczek
    
    PenColour = "Black"
    """
    Zmienna określająca kolor konturu piłeczki.
    """
    PenWidth = 1
    """
    Zmienna określająca szerokość konturu piłeczki.
    """
    PenStyle = wx.PENSTYLE_SOLID
    """
    Zmienna określająca styl rysowania konturu piłeczki.
    """
    BrushColour = wx.Colour(255, 206, 80, 255)
    """
    Zmienna określająca kolor wypełnienia piłeczki.
    """
    BrushStyle = wx.BRUSHSTYLE_SOLID
    """
    Zmienna określająca styl wypełnienia piłeczki.
    """
    
    
    def __init__(self, Radius, Position, Mass, Velocity):
        """
        Konstruktor tworzy obiekt piłeczki o podanych parametrach.
        
        @param Radius: Parametr określający promień piłeczki.
        @param Position: Parametr określający punkt będący pozycją piłeczki.
        @param Mass: Parametr określający masę piłeczki.
        @param Velocity: Parametr określający prędkość piłeczki.
        """
        
        self._Radius = Radius
        self._Position = Position
        self._Mass = Mass
        self._Velocity = Velocity
    
    
    def getPosition(self):
        """
        Funkcja zwracająca położenie piłeczki.
        
        @return: Pozycję piłeczki.
        """

        return self._Position
    
    
    def setPosition(self, Position):
        """
        Procedura ustawiająca położenie piłeczki.
        
        @param Position: Parametr określający pozycję piłeczki.
        """
        
        self._Position = Position
    
    
    def setPositionX(self, positionX):
        """
        Procedura ustawiająca położenie X piłeczki.
        
        @param positionX: Parametr określający pozycję piłeczki na osi X.
        """
        
        self._Position.setPositionX(positionX)
    
    
    def setPositionY(self, positionY):
        """
        Procedura ustawiająca położenie Y piłeczki.
        
        @param positionY: Parametr określający pozycję piłeczki na osi Y.
        """
        
        self._Position.setPositionY(positionY)
    
    
    def setVelocity(self, NewVelocity):
        """
        Procedura ustawiająca nową prędkość piłeczki.
        
        @param NewVelocity: Parametr określający nową wartość prędkości piłeczki.
        """
        
        self._Velocity = NewVelocity
    
    
    def getVelocity(self):
        """
        Funkcja zwracająca prędkość piłeczki.
        
        @return: Prędkość piłeczki.
        """
        
        return self._Velocity
    
    
    def getRadius(self):
        """
        Funkcja zwracająca promień piłeczki
        
        @return: Promień piłeczki.
        """

        return self._Radius
    
    
    def getMass(self):
        """
        Funkcja zwracająca masę piłeczki.
        
        @return: Masę piłeczki.
        """
        
        return self._Mass
    
    
    def collidesWith(self, anotherBall):
        """
        Funkcja sprawdzająca, czy piłeczka koliduje z piłeczką przekazaną w
        parametrze.
        
        Sprawdzenie polega na porównaniu czy odległość między
        piłeczkami jest większa niż suma ich promieni. Jeżeli została wybrana 
        tylko jedna piłeczka to kolizja jest niemożliwa.
        
        @param anotherBall: Parametr określający inną piłeczkę, 
        z którą może wystąpić kolizja.
        
        @return: 
            1. Jeśli wybrana została tylko jedna piłeczka - zwracana jest 
               wartość B{False} (kolizja jest niemożliwa). 
            2. Jeżeli odległość pomiedzy 
               środkami piłeczek jest mniejsza, lub równa sumie ich promieni - 
               zwracana jest wartość B{True} (piłeczki kolidują ze sobą). 
            3. Jeżeli odległość pomiedzy środkami piłeczek jest większa niż 
               suma ich promieni - zwracana jest wartość B{False} 
               (piłeczki nie kolidują ze sobą)
        """
        
        if self == anotherBall:
            return False
        else:
            Distance = self.getPosition().getDistanceToPoint(anotherBall.getPosition())
            
            #if round(Distance,Options.PositionRoundingDigits) <= round(self.getRadius() + anotherBall.getRadius(),Options.PositionRoundingDigits):
            if Distance <= self.getRadius() + anotherBall.getRadius():
                return True
            else:
                return False
    
    
    def collidesWithWall(self, wall):
        """
        Funkcja sprawdzająca, czy piłeczka koliduje ze ścianą przekazaną w
        parametrze.
        
        Sprawdzenie polega na porównaniu wysokości trójkąta zbudowanego 
        z trzech punktów A,B,C (gdzie A i B należą do ściany, a C jest 
        środkiem sprawdzanej piłeczki) oraz promienia piłeczki.
        
        @param wall: Parametr określający ścianę, 
            z którą może wystąpić kolizja.
        
        @return:
            1. Jeżeli wysokość trójkąta ABC jest mniejsza, lub równa długości 
               promienia - zracana jest wartość B{True} (nastąpiła kolizja)
            2. Jeżeli wysokość trójkąta ABC jest większa niż długości promienia
               - zwracana jest wartość B{False} (nie nastąpiła kolizja)
        """
        
        # źródło: http://en.wikipedia.org/wiki/Triangle#Using_coordinates
        
        # punkty definiujące ścianę
        A = wall.getPoint1()
        Ax = A.getPositionX()
        Ay = A.getPositionY()
        B = wall.getPoint2()
        Bx = B.getPositionX()
        By = B.getPositionY()
        
        # punkt, który testujemy
        Cx = self.getPosition().getPositionX()
        Cy = self.getPosition().getPositionY()
        
        # powierzchnia trójkąta zbudowanego z tych trzech punktów
        TriangleArea = 0.5 * abs((Ax-Cx)*(By-Ay) - (Ax-Bx)*(Cy-Ay))
        
        # długość ściany
        ABLength = A.getDistanceToPoint(B)
        
        # wysokość trójkąta zbudowanego z tych trzech punktów, która jest równa
        # odległości punktu od prostej przechodzącej przez ścianę
        TriangleHeight = 2*TriangleArea/ABLength
        
        
        if TriangleHeight <= self.getRadius():
            return True
        else:
            return False
    
    
#    def resolveCollision(self, ball):
#        """
#        Funkcja obsługująca kolizję między dwiema piłeczkami.
#        """
#        
#        # get the mtd
#        delta = Velocity(self.getPosition().getPositionX() - ball.getPosition().getPositionX(), self.getPosition().getPositionY() - ball.getPosition().getPositionY())
#        r = self.getRadius() + ball.getRadius()
#        dist2 = delta.dot(delta)
#        
#        if dist2 > r*r:
#            return # they aren't colliding
#        
#        
#        d = delta.getVectorLength()
#        
#        mtd = Velocity()
#        
#        if d != 0.0:
#            tempVector = deepcopy(delta)
#            tempVector.multiplyVector(((self.getRadius() + ball.getRadius())-d)/d) # minimum translation distance to push balls apart after intersecting
#            mtd = deepcopy(tempVector)
#        else: # Special case. Balls are exactly on top of eachother.  Don't want to divide by zero.
#            d = ball.getRadius() + self.getRadius() - 1.0
#            delta = Velocity(ball.getRadius() + self.getRadius(), 0.0)
#            tempVector = deepcopy(delta)
#            tempVector.multiplyVector(((self.getRadius() + ball.getRadius())-d)/d)
#            mtd = deepcopy(tempVector)
#
#        # resolve intersection
#        im1 = 1 / self.getMass() # inverse mass quantities
#        im2 = 1 / ball.getMass()
#
#        # push-pull them apart
#        tempVector = deepcopy(mtd)
#        tempVector.multiplyVector(im1 / (im1 + im2))
#        self.getPosition().moveByVector(tempVector)
#        tempVector = deepcopy(mtd)
#        tempVector.multiplyVector(im2 / (im1 + im2))
#        ball.getPosition().moveAgainstVector(tempVector)
#
#        # impact speed
#        tempVector = deepcopy(self.getVelocity())
#        tempVector.substractVector(ball.getVelocity())
#        v = deepcopy(tempVector)
#        vn = v.dot(mtd.normalize())
#
#        # sphere intersecting but moving away from each other already
#        if (vn > 0.0):
#            return
#
#        # collision impulse
#        i = (-(1.0 + Options.CoefficientOfRestitution) * vn) / (im1 + im2)
#        tempVector = deepcopy(mtd)
#        tempVector.multiplyVector(i)
#        impulse = deepcopy(tempVector)
#
#        # change in momentum
#        tempVector = deepcopy(impulse)
#        tempVector.multiplyVector(im1)
#        tempVector.addVector(self.getVelocity())
#        self.setVelocity(tempVector)
#        tempVector = deepcopy(impulse)
#        tempVector.multiplyVector(im2)
#        tempVector2 = deepcopy(self.getVelocity())
#        tempVector2.substractVector(tempVector)
#        ball.setVelocity(tempVector2)
    
    
    def draw(self, Dc, CanvasPosition, paintVelocity):
        """
        Procedura rysująca piłeczkę na płótnie w miejscu podanych
        współrzędnych.
        
        @param Dc: Parametr ustawiający sposób rysowania piłeczki na podstawie
            atrubutów klasy. 
        
        @param CanvasPosition: Parametr określający pozycję płótna.
        
        @param paintVelocity: Parametr odpowiadający za rysowanie wektorów
            prędkości piłeczek.
        """
        
        CanvasPositionX = CanvasPosition.getPositionX()
        CanvasPositionY = CanvasPosition.getPositionY()
        
        Dc.SetPen(wx.Pen(Ball.PenColour, Ball.PenWidth, Ball.PenStyle))
        Dc.SetBrush(wx.Brush(Ball.BrushColour, Ball.BrushStyle))
        
        Dc.DrawCircle(CanvasPositionX, CanvasPositionY, self.getRadius())
        Dc.DrawLabel("{0}".format(self.getMass()), (CanvasPositionX-self.getRadius()/2, CanvasPositionY-self.getRadius()/2, self.getRadius(), self.getRadius()), wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)
        
        if paintVelocity:
            Point1X = CanvasPositionX
            Point1Y = CanvasPositionY
            Point2X = CanvasPositionX + self.getVelocity().getVx()
            Point2Y = CanvasPositionY - self.getVelocity().getVy()
            Dc.DrawLine(Point1X, Point1Y, Point2X, Point2Y)